package org.apache.james.hibernate;

import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Random;

import javax.mail.Flags;

import org.apache.james.hibernate.entity.DbMailbox;
import org.apache.james.hibernate.entity.Group;
import org.apache.james.hibernate.entity.ImapUserInfo;
import org.apache.james.hibernate.entity.Mail;
import org.apache.james.hibernate.entity.MailBody;
import org.apache.james.hibernate.entity.MailFlag;
import org.apache.james.hibernate.entity.MailHeader;
import org.apache.james.hibernate.entity.NamespaceImpl;
import org.apache.james.mailboxmanager.GeneralMessageSet;
import org.apache.james.mailboxmanager.MailboxManagerException;
import org.apache.james.mailboxmanager.MessageResult;
import org.apache.james.mailboxmanager.Namespace;
import org.apache.james.mailboxmanager.Namespaces;
import org.apache.james.mailboxmanager.SearchParameters;
import org.apache.james.mailboxmanager.SearchParameters.HeaderSearchCriteria;
import org.apache.james.mailboxmanager.SearchParameters.SearchCriteria;
import org.apache.james.mailboxmanager.impl.MessageResultImpl;
import org.apache.james.mailboxmanager.impl.MessageResultWithHeaders;
import org.apache.log4j.Logger;
import org.hibernate.Criteria;
import org.hibernate.LockMode;
import org.hibernate.Query;
import org.hibernate.SessionFactory;
import org.hibernate.classic.Session;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;
import org.springframework.transaction.annotation.Transactional;

@Transactional
public class DbMailboxManager implements MailStore, MailboxPersistenceManager {

	private final static Logger LOG = Logger.getLogger(DbMailboxManager.class);
	
	private SessionFactory sessionFactory;

	private Random random = new Random();
	
	private MailStore innerMailStore;
	
	public void setSessionFactory(SessionFactory sessionFactory) {
		this.sessionFactory = sessionFactory;
	}
	
	@Override
	public MailStore getMailStore() {
		return innerMailStore;
	}
	
	/* (non-Javadoc)
	 * @see org.apache.james.hibernate.MailboxPersistenceManager#setupMailStore(org.apache.james.hibernate.MailStore)
	 */
	@Override
	public void setupMailStore(MailStore pInnerMailStore) {
		if (innerMailStore!=null) {
			throw new RuntimeException("inner Mail store is already initialized!");
		}
		if (pInnerMailStore == null) {
			LOG.warn("init db mailbox manager without explicitly specified mailstore, fallback to SELF, this meens that the body of the mail is stored in the same database also.");
			pInnerMailStore = this;
		} else {
			LOG.info("init db mailbox manager with "
					+ pInnerMailStore.getClass().getName() + ":"
					+ pInnerMailStore);
		}
		this.innerMailStore = pInnerMailStore;
	}
	

	/* (non-Javadoc)
	 * @see org.apache.james.hibernate.MailboxPersistenceManager#getUserinfo(java.lang.String)
	 */
	@Override
	public ImapUserInfo getUserinfo(String username) {
		Query query = this.sessionFactory.getCurrentSession().createQuery(
				"from " + ImapUserInfo.class.getName() + " where name = ?");
		query.setParameter(0, username);
		ImapUserInfo userInfo = (ImapUserInfo) query.uniqueResult();
		if (userInfo == null) {
			userInfo = createImapUserInfo(username);
		}
		userInfo.getGroups();
		return userInfo;
		
	}

	/* (non-Javadoc)
	 * @see org.apache.james.hibernate.MailboxPersistenceManager#createNewNamespace(java.lang.String, java.lang.String, java.lang.String, boolean, boolean)
	 */
	public NamespaceImpl createNewNamespace(String name, String description,
			String repository, boolean defaultShared,
			boolean defaultUser) {
		NamespaceImpl ns = new NamespaceImpl();
		ns.setName(name);
		ns.setRepository(repository);
		ns.setDescription(description);
		ns.setDefaultShared(defaultShared);
		ns.setDefaultUser(defaultUser);
		this.sessionFactory.getCurrentSession().saveOrUpdate(ns);
		return ns;
	}

	/* (non-Javadoc)
	 * @see org.apache.james.hibernate.MailboxPersistenceManager#getGroup(java.lang.String)
	 */
	@Override
	public Group getGroup(String name) {
		Query query = this.sessionFactory.getCurrentSession().createQuery(
				"from " + Group.class.getName() + " where name = ?");
		query.setParameter(0, name);
		return (Group) query.uniqueResult();
	}

	/* (non-Javadoc)
	 * @see org.apache.james.hibernate.MailboxPersistenceManager#getNamespace(java.lang.String)
	 */
	@Override
	public NamespaceImpl getNamespace(String name) {
		Query query = this.sessionFactory.getCurrentSession().createQuery(
				"from " + NamespaceImpl.class.getName() + " where name = ?");
		query.setParameter(0, name);
		return (NamespaceImpl) query.uniqueResult();
	}

	/* (non-Javadoc)
	 * @see org.apache.james.hibernate.MailboxPersistenceManager#getUserNamespaces(java.lang.String)
	 */
	@Override
	public Namespaces getUserNamespaces(String name) {
		return getUserinfo(name).getNamespaceInfo();
	}

	/* (non-Javadoc)
	 * @see org.apache.james.hibernate.MailboxPersistenceManager#createImapUserInfo(java.lang.String)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public ImapUserInfo createImapUserInfo(String username) {
		ImapUserInfo userInfo = new ImapUserInfo();
		userInfo.setName(username);

		List<NamespaceImpl> list = getDefaultNamespaces("defaultUser");

		if (list.size()>0) {
			userInfo.setDefaultNamespace(list.get(0));
		}
		userInfo.setUserNameSpaces(new HashSet(list));

		
		userInfo.setSharedNameSpaces(new HashSet(getDefaultNamespaces("defaultShared")));
		
		userInfo = (ImapUserInfo) sessionFactory.getCurrentSession().merge(userInfo);
		
		// creating the default INBOX
		//String inboxName = ((NamespaceImpl) userInfo.getNamespaceInfo().getPersonalDefault()).getMailboxName("INBOX");
		//createMailbox(userInfo, inboxName);
		return userInfo;
	}
	
	
	/* (non-Javadoc)
	 * @see org.apache.james.hibernate.MailboxPersistenceManager#appendAsNewMail(org.apache.james.hibernate.entity.ImapUserInfo, org.apache.james.hibernate.entity.DbMailbox, org.apache.james.hibernate.entity.Mail, javax.mail.Flags)
	 */
	@Override
	public MailFlag appendAsNewMail(ImapUserInfo user, DbMailbox mailbox, Mail mail, Flags javamailFlag) {
		Session ses = sessionFactory.getCurrentSession();
		ses.lock(mailbox, LockMode.UPGRADE);
		mailbox.setUid(mailbox.getUid()+1);
		
		mail.setUid(mailbox.getUid());
		if (mail.getKey()==null) {
			// it seems that when copying from a local account to the server with kmail, there is no such thing, as key
			mail.setKey("james-message-id-"+Math.random()*1000);
		}
		
		MailFlag mf = new MailFlag();
		mf.setMail(mail);
		mf.setMailbox(mailbox);
		mf.setUser(user);
		mf.setUid(mailbox.getUid());
		mf.setFlags(javamailFlag);
		mail.setNamespace(mailbox.getNamespace());
		
		ses.update(mailbox);
		ses.persist(mf);
		
		mail.onPersist();
		for (MailHeader header : mail.getHeaders()) {
			ses.persist(header);
		}
		
		return mf;
	}

	/**
	 * return a list of default namespaces (which added automaticly to every user)
	 * @param type possible values: 'defaultShared', 'defaultUser'
	 * @return list of namespaces
	 */
	private List<NamespaceImpl> getDefaultNamespaces(String type) {
		Query query = this.sessionFactory.getCurrentSession().createQuery(
				"from " + NamespaceImpl.class.getName() + " where " + type
						+ " = ? order by id");
		query.setBoolean(0, true);

		List<NamespaceImpl> list = (List<NamespaceImpl>) query.list();
		return list;
	}

	/* (non-Javadoc)
	 * @see org.apache.james.hibernate.MailboxPersistenceManager#createMailbox(org.apache.james.hibernate.entity.ImapUserInfo, org.apache.james.hibernate.entity.NamespaceImpl, java.lang.String)
	 */
	@Override
	public DbMailbox createMailbox(ImapUserInfo userInfo, NamespaceImpl ns, String mailboxName) {

		DbMailbox mailbox = new DbMailbox();
		mailbox.setName(mailboxName);
		mailbox.setOwner(userInfo);
		mailbox.setUid(0);
		mailbox.setUidValidity(Math.abs(random.nextInt()));
		mailbox.setNamespace(ns);
		
		sessionFactory.getCurrentSession().persist(mailbox);
		return mailbox;
	}

	/* (non-Javadoc)
	 * @see org.apache.james.hibernate.MailboxPersistenceManager#existsMailbox(org.apache.james.hibernate.entity.ImapUserInfo, java.lang.String)
	 */
	@Override
	public boolean existsMailbox(ImapUserInfo userInfo, String mailboxName) {
		Query query = sessionFactory.getCurrentSession().createQuery("select count(*) from "+DbMailbox.class.getName()+
				" m where (m.name = :name and m.owner.id = :ownerid)");
		query.setString("name", mailboxName);
		query.setInteger("ownerid", userInfo.getId());
		return ((Number)query.uniqueResult()).intValue()>0;
	}

	/* (non-Javadoc)
	 * @see org.apache.james.hibernate.MailboxPersistenceManager#createInbox(org.apache.james.hibernate.entity.ImapUserInfo)
	 */
	@Override
	public boolean createInbox(ImapUserInfo userInfo) {
		NamespaceImpl personalNamespace = ((NamespaceImpl)userInfo.getNamespaceInfo().getPersonalDefault());
		String inboxName = personalNamespace.getMailboxName("INBOX");
		if (!existsMailbox(userInfo, inboxName)) {
			DbMailbox mailbox = createMailbox(userInfo, userInfo.getDefaultNamespace(), inboxName);

			createMailbox(userInfo, userInfo.getDefaultNamespace(), personalNamespace.getMailboxName("Trash"));
			createMailbox(userInfo, userInfo.getDefaultNamespace(), personalNamespace.getMailboxName("Drafts"));
			
			/*
			Mail mail = new Mail();
			mail.setInternalDate(new Date());
			mail.setUid(1);
			mail.setKey("WELCOME_MESSAGE");
			MailBody mb = new MailBody();
			mb.setMailId(1);
			mb.setBody("Hello world".getBytes());
			mail.setMailBody(mb);
			Flags flags = new Flags();
			flags.add(Flag.RECENT);
			appendAsNewMail(userInfo, mailbox, mail, flags);//*/
			return true;
		}
		return false;
	}

	/* (non-Javadoc)
	 * @see org.apache.james.hibernate.MailboxPersistenceManager#listMailboxNames(org.apache.james.hibernate.entity.ImapUserInfo, java.lang.String, java.lang.String, boolean)
	 */
	@SuppressWarnings("unchecked")
	public List<String> listMailboxNames(ImapUserInfo userInfo, String base,
			String expression, boolean subscribed) {
		Query query = sessionFactory.getCurrentSession().createQuery("select m.name from "+DbMailbox.class.getName() + " m where (m.name like :name and (m.owner.id = :ownerid or m.owner is null))");
		query.setString("name", base + (expression.replace('*', '%')));
		query.setInteger("ownerid", userInfo.getId());
		
		return query.list();
	}

	/* (non-Javadoc)
	 * @see org.apache.james.hibernate.MailboxPersistenceManager#deleteMailbox(org.apache.james.hibernate.entity.ImapUserInfo, java.lang.String)
	 */
	@Override
	public void deleteMailbox(ImapUserInfo userInfo, String mailboxName) {
		Query query = sessionFactory.getCurrentSession().
			createQuery(
						"delete from "
								+ DbMailbox.class.getName()
								+ " m where (m.name = :name and m.owner.id = :ownerid)");

		query.setString("name", mailboxName);
		query.setInteger("ownerid", userInfo.getId());

		query.executeUpdate();
	}

	/* (non-Javadoc)
	 * @see org.apache.james.hibernate.MailboxPersistenceManager#getMailbox(java.lang.String)
	 */
	@Override
	public DbMailbox getMailbox(String mailboxName) {
		Query query = sessionFactory
				.getCurrentSession()
				.createQuery(
						" from "
								+ DbMailbox.class.getName()
								+ " m where m.name = :name ");
		query.setString("name", mailboxName);
		List<DbMailbox> list = query.list();
		if (list.size()>0) {
			return (DbMailbox) list.get(0);
		}
		return null;
	}

	/* (non-Javadoc)
	 * @see org.apache.james.hibernate.MailboxPersistenceManager#getMailbox(int)
	 */
	@Override
	public DbMailbox getMailbox(int id) {
		return (DbMailbox) sessionFactory.getCurrentSession().load(DbMailbox.class, new Integer(id));
	}

	/* (non-Javadoc)
	 * @see org.apache.james.hibernate.MailboxPersistenceManager#getMessagesFlags(org.apache.james.mailboxmanager.GeneralMessageSet, org.apache.james.hibernate.entity.ImapUserInfo, org.apache.james.hibernate.entity.DbMailbox)
	 */
	@SuppressWarnings("unchecked")
	public MessageResult[] getMessagesFlags(GeneralMessageSet set, ImapUserInfo user, DbMailbox mailbox) throws MailboxManagerException {
		Criteria q = createCriteria(set, user, mailbox);
		List<MailFlag> list = q.list();
		MessageResult[] result = new MessageResult[list.size()];
		for (int i=0;i<list.size();i++) {
			MailFlag mf = list.get(i);
			result[i] = new MessageResultImpl(mf.getUid(), mf.getFlagsObject());
		}
		return result;
	}

	/* (non-Javadoc)
	 * @see org.apache.james.hibernate.MailboxPersistenceManager#getMessages(org.apache.james.mailboxmanager.GeneralMessageSet, org.apache.james.hibernate.entity.ImapUserInfo, org.apache.james.hibernate.entity.DbMailbox, boolean, boolean)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public MessageResult[] getMessages(GeneralMessageSet set, ImapUserInfo user, DbMailbox mailbox, boolean includeBody, boolean includeHeader) throws MailboxManagerException {
		Criteria q = createCriteria(set, user, mailbox);
		List<MailFlag> list = q.list();
		MessageResult[] result = new MessageResult[list.size()];
		for (int i=0;i<list.size();i++) {
			MailFlag mf = list.get(i);
			MessageResultWithHeaders m = new MessageResultWithHeaders(mf.getUid(), mf.getFlagsObject());
			m.setSize(mf.getMail().getSize());
			m.setInternalDate(mf.getMail().getInternalDate());
			m.setKey(mf.getMail().getKey());
			m.setBodyStructure(mf.getMail().getBodyStructure());
			// TODO: when we can calculate the extensions also, we should get that value instead of duplicating the same
			m.setBodyStructureWithExtensions(mf.getMail().getBodyStructure());
			m.setEnvelope(mf.getMail().getEnvelope());
			
			if (includeHeader) {
				for (MailHeader header : mf.getMail().getHeaders()) {
					m.addHeader(header.getName(), header.getValue());
				}
			}
			if (includeBody) {
				// TODO: add separate body fetching
				//byte[] content = mf.getMail().getMailBody().getBody();
				byte[] content = innerMailStore.getMailBody(mf.getMail());
				m.setMimeMessageBody(content);
			}
			result[i] = m;
		}
		return result;
	}

	private String createWhereString(GeneralMessageSet set) throws MailboxManagerException {
		String q = " where m.user.id = :userid and m.mailbox.id = :mailboxid ";
		if (set.getType() == GeneralMessageSet.TYPE_ALL) {
			
		} else if (set.getType()== GeneralMessageSet.TYPE_UID) {
			if (set.getUidFrom()== set.getUidTo()) {
				q += " and m.uid = :uid";
			} else {
				q += " and m.uid >= :uidfrom";
				if (set.getUidTo() > 0) {
					q += " and m.uid <= :uidto";
				}
			}
		} else {
            throw new MailboxManagerException("unsupported MessageSet: "
                    + set.getType());
		}
		return q;
	}
	
	private void setupQuery(Query q, GeneralMessageSet set) throws MailboxManagerException {
		if (set.getType() == GeneralMessageSet.TYPE_ALL) {
			
		} else if (set.getType()== GeneralMessageSet.TYPE_UID) {
			if (set.getUidFrom()== set.getUidTo()) {
				q.setLong("uid", set.getUidFrom());
			} else {
				q.setLong("uidfrom", set.getUidFrom());
				if (set.getUidTo() > 0) {
					q.setLong("uidto", set.getUidTo());
				}
			}
		} else {
            throw new MailboxManagerException("unsupported MessageSet: "
                    + set.getType());
		}
		
	}
	
	private Criteria createCriteria(GeneralMessageSet set, ImapUserInfo user, DbMailbox mailbox) throws MailboxManagerException {
		Criteria criteria = sessionFactory.getCurrentSession().createCriteria(MailFlag.class);
		criteria.addOrder(Order.asc("uid"));
		criteria.add(Restrictions.eq("user", user));
		// todo: check this is required? 
		criteria.add(Restrictions.eq("mailbox", mailbox));
		
        if (set.getType() == GeneralMessageSet.TYPE_ALL) {
            // empty Criteria = everything
        } else if (set.getType() == GeneralMessageSet.TYPE_UID) {
            
            if (set.getUidFrom() == set.getUidTo()) {
                criteria.add(Restrictions.eq("uid", new Long(set.getUidFrom())));
            } else {
            	criteria.add(Restrictions.ge("uid", new Long(set.getUidFrom())));
                if (set.getUidTo() > 0) {
                	criteria.add(Restrictions.le("uid", new Long(set.getUidTo())));
                }
            }
        } else {
            throw new MailboxManagerException("unsupported MessageSet: "
                    + set.getType());
        }

		return criteria;
	}

	/* (non-Javadoc)
	 * @see org.apache.james.hibernate.MailboxPersistenceManager#getMessageCount(org.apache.james.hibernate.entity.DbMailbox, org.apache.james.hibernate.entity.ImapUserInfo, java.lang.String)
	 */
	@Override
	public int getMessageCount(DbMailbox mailbox, ImapUserInfo user, String flags) {
		Query q = sessionFactory.getCurrentSession().createQuery(
				"select count(*) from " + MailFlag.class.getName()
						+ " a where a.mailbox.id = :mailboxid and a.user.id = :userid"
						+ (flags != null ? " and (" + flags + ")" : ""));
		setupQuery(q, mailbox, user);
		return ((Number)q.uniqueResult()).intValue();
	}

	/* (non-Javadoc)
	 * @see org.apache.james.hibernate.MailboxPersistenceManager#getFirstUnseenUid(org.apache.james.hibernate.entity.DbMailbox, org.apache.james.hibernate.entity.ImapUserInfo)
	 */
	@Override
	public Long getFirstUnseenUid(DbMailbox mailbox, ImapUserInfo user) {
		// TODO: modify to return the FIRST, not one random.
		Query q = sessionFactory.getCurrentSession().createQuery("select min(a.uid) from "+MailFlag.class.getName() + 
				" a where a.mailbox.id = :mailboxid and a.user.id = :userid and a.seen = false");
		setupQuery(q, mailbox, user);
		q.setFetchSize(1);
		return (Long) q.uniqueResult();
	}

	private void setupQuery(Query q, DbMailbox mailbox, ImapUserInfo user) {
		q.setInteger("mailboxid", mailbox.getId());
		q.setInteger("userid", user.getId());
	}


	/* (non-Javadoc)
	 * @see org.apache.james.hibernate.MailboxPersistenceManager#setFlags(javax.mail.Flags, boolean, boolean, org.apache.james.mailboxmanager.GeneralMessageSet, org.apache.james.hibernate.entity.DbMailbox, org.apache.james.hibernate.entity.ImapUserInfo)
	 */
	@Override
	public void setFlags(Flags flags, boolean replace, boolean addFlags,
			GeneralMessageSet set, DbMailbox mailbox, ImapUserInfo user) throws MailboxManagerException {

		String updateString = MailFlag.format(flags, replace, addFlags);
		String whereString = createWhereString(set);
		
		String update = "update "+MailFlag.class.getName()+ " m SET "+updateString+ " "+whereString;
		
		Query updateQuery = sessionFactory.getCurrentSession().createQuery(update);
		setupQuery(updateQuery, mailbox, user);
		setupQuery(updateQuery, set);
		
		updateQuery.executeUpdate();
	}

	/* (non-Javadoc)
	 * @see org.apache.james.hibernate.MailboxPersistenceManager#searchUids(org.apache.james.hibernate.entity.DbMailbox, org.apache.james.hibernate.entity.ImapUserInfo, org.apache.james.mailboxmanager.SearchParameters)
	 */
	@Override
	public MessageResult[] searchUids(DbMailbox mailbox, ImapUserInfo user,
			SearchParameters searchTerm) throws MailboxManagerException {
		//"select mf.uid from "+MailFlag.class.getName()+" mf, "

		QueryBuilder qb = new QueryBuilder(MailFlag.class,"mf");
		qb.setSelect("mf.uid");
		qb.addWhereWithParameter("mf.mailbox.id = ?", mailbox.getId());
		qb.addWhereWithParameter("mf.user.id = ?", user.getId());
		
		for (Iterator it = searchTerm.getCriterias().iterator(); it.hasNext(); ) {
			SearchParameters.SearchCriteria crit = (SearchCriteria) it.next();
			String name = crit.getName();
			if (!handleBasicSearchTerms(qb, name)) {
				if (crit instanceof HeaderSearchCriteria) {
					HeaderSearchCriteria hCrit = (HeaderSearchCriteria) crit;
//					qb.addWhere("mf.mail.id IN (select header.mailId from "+MailHeader.class.getName()+" header")
					qb.addWhere("mf.mail.id IN (SELECT header.mailId FROM "+MailHeader.class.getName()+" header WHERE header.name = ? AND header.value like ?)");
					qb.addParameter(hCrit.getFieldName());
					qb.addParameter("%" + hCrit.getValue() + "%");
				} else {
					throw new MailboxManagerException("Search term:"+crit+" not supported by this backend!");
				}
			}
		}
		Query query = qb.createQuery(sessionFactory.getCurrentSession());
		List result = query.list();
		MessageResult[] uids = new MessageResult[result.size()];
		for (int i=0;i<result.size();i++) {
			uids[i] = new MessageResultImpl(((Number)result.get(i)).longValue());
		}
		return uids;
	}

	private boolean handleBasicSearchTerms(QueryBuilder qb, String name) {
		for (int i=0;i<QUERY_PARAM_MAP.length;i++) {
			if (QUERY_PARAM_MAP[i][0].equals(name)) {
				if (QUERY_PARAM_MAP[i][0]!=null) {
					qb.addWhere(QUERY_PARAM_MAP[i][1]);
				}
				return true;
			}
		}
		return false;
	}
	
	
	final static String[][] QUERY_PARAM_MAP = new String[][] { 
			{ "ALL", null },
			{ "ANSWERED", "mf.answered = true" }, 
			{ "DELETED", "mf.deleted = true" }, 
			{ "DRAFT", "mf.draft = true" },
			{ "FLAGGED", "mf.flagged = true" }, 
			{ "NEW", "mf.recent = true AND mf.seen = false" }, 
			{ "OLD", "mf.recent = false" },
			{ "RECENT", "mf.recent = true" }, 
			{ "SEEN", "mf.seen = true" }, 
			{ "UNANSWERED", "mf.answered = false" },
			{ "UNDELETED", "mf.deleted = false" }, 
			{ "UNDRAFT", "mf.draft = false" }, 
			{ "UNFLAGGED", "mf.flagged = false"},
			{ "UNSEEN", "mf.seen = false" } };

	/* (non-Javadoc)
	 * @see org.apache.james.hibernate.MailboxPersistenceManager#expungeAll(org.apache.james.hibernate.entity.DbMailbox, org.apache.james.hibernate.entity.ImapUserInfo)
	 */
	@Override
	public List<Integer> expungeAll(DbMailbox mailbox, ImapUserInfo user) {
		// TODO : implement proper access checking
		if (user.equals(mailbox.getOwner())) {
			// unfortunately (or not), we have to manually remove all references
			// by hand. (this is necessary, because sometimes we store the mailbody
			// in an external place. (not in the same database) And this is the reason why we have to
			// explicitly list the needed ID-s, to keep it synchronized.
			Session ses = sessionFactory.getCurrentSession();
			Query query = ses
					.createQuery("select f.mail.id from "
							+ MailFlag.class.getName()
							+ " f "
							+ "where f.deleted = true and f.user.id = :userid and f.mailbox.id = :mailboxid");
			setupQuery(query, mailbox, user);
			List<Integer> ids =  query.list();
			if (ids.size()>0) {
				String idString = toIdStringList(ids);
				// probably we should use some built-in cascading.
				ses.createQuery("delete from "
						+ MailHeader.class.getName() + " m where m.mailId in ("
						+ idString + ')').executeUpdate();
				ses.createQuery("delete from "
						+ MailFlag.class.getName() + " m where m.mail.id in ("
						+ idString + ')').executeUpdate();
				ses.createQuery("delete from "
						+ Mail.class.getName() + " m where m.id in ("
						+ idString + ')').executeUpdate();
			}
			ses.flush();
			innerMailStore.removeMailBody(ids);
			return ids;
		}
		return Collections.EMPTY_LIST;
	}

	
	private String toIdStringList(List<Integer> ids) {
		StringBuilder s = new StringBuilder();
		for (int i=0;i<ids.size();i++) {
			if (i>0) {
				s.append(',');
			}
			s.append(ids.get(i).intValue());
		}
		return s.toString();
	}

	@Override
	public void renameMailbox(ImapUserInfo userInfo, String from, String to) throws MailboxManagerException {

		DbMailbox mailbox = getMailbox(from);
		if (mailbox!=null) {
			if (userInfo.equals(mailbox.getOwner())) {
				NamespaceImpl namespace = mailbox.getNamespace();
				String namespaceName = namespace.getName();
				if (to.startsWith(namespaceName)) {
					// check for mail.user1.ize to mail.user2.bigyo style renamings, because it's forbidden! :)
					Namespace fromNamespace = userInfo.getNamespaceFromMailbox(from);
					Namespace toNamespace = userInfo.getNamespaceFromMailbox(to);
					if (toNamespace==null) {
						throw new MailboxManagerException("Destination namespace is not accessible!");
					}
					if (toNamespace.getName().equals(fromNamespace.getName())) {
						mailbox.setName(to);
						LOG.info("rename mailbox '"+from+"' to '"+to+"' by "+userInfo.getName());
						sessionFactory.getCurrentSession().update(mailbox);
					} else {
						throw new MailboxManagerException("Rename across different namespaces is not supported!["+fromNamespace.getName()+" to "+toNamespace.getName()+']');
					}
				} else {
					throw new MailboxManagerException("Rename across different namespaces is not supported!["+to+" not in "+namespaceName+']');
				}
			} else {
				throw new MailboxManagerException("Rename of a not-owned mailbox is not allowed! ['"+mailbox+"']");
			}
		} else {
			throw new MailboxManagerException("Mailbox '"+from+"' not found!");
		}
		
	}

	// the internal MailStore implementation.
	
	@Override
	public byte[] getMailBody(Mail mail) {
		return mail.getMailBody().getBody();
	}
	
	@Override
	public void persistMailBody(Mail mail, byte[] byteBuffer) {
		MailBody body = new MailBody();
		body.setBody(byteBuffer);
		body.setMailId(mail.getId().intValue());
		sessionFactory.getCurrentSession().persist(body);
	}
	
	@Override
	public void removeMailBody(List<Integer> mailIds) {
		if (mailIds!=null && mailIds.size()>0) {
			Session ses = sessionFactory.getCurrentSession();
			String idString = toIdStringList(mailIds);
			ses.createQuery("delete from "
					+ MailBody.class.getName() + " m where m.mailId in ("
					+ idString + ')').executeUpdate();
			ses.flush();
		}
	}

	
}
